using System.Drawing;
using System.Threading;
using System.Collections;

using Math;

namespace Fractal
{
    public class Mandelbrot
    {
        // Public methods

        public Mandelbrot(Fractal.CalculationParameters cpIn, ThreadStart updateCallbackIn, ThreadStart finishCallbackIn)
        {
            cp_ = cpIn;
            updateCallback_ = updateCallbackIn;
            finishCallback_ = finishCallbackIn;
            bitmap_ = new System.Drawing.Bitmap(cp_.pixelWidth, cp_.pixelHeight);
            Graphics g = Graphics.FromImage(bitmap_);
            g.FillRectangle(System.Drawing.Brushes.Green, 0, 0, bitmap_.Width, bitmap_.Height);
        }

        public void render()
        {
            updateCallback_();
            if(cp_.calculationType == CalculationParameters.CalculationType.SinglePoint)
            {
                renderSinglePoint();
            }
            else if(cp_.calculationType == CalculationParameters.CalculationType.Interval)
            {
                renderIntervalRecursive(new Math.ComplexInterval(
                    new Math.Interval(cp_.left, cp_.right), 
                    new Math.Interval(cp_.bottom, cp_.top)),
                    new Boundary(0, 0, cp_.pixelWidth, cp_.pixelHeight));
            }

            finishCallback_.Invoke();
        }

        public System.Drawing.Bitmap getBitmap()
        {
            return (Bitmap)bitmap_.Clone();
        }

        // Member variables

        CalculationParameters cp_;
        System.Drawing.Bitmap bitmap_;
        ThreadStart updateCallback_;
        ThreadStart finishCallback_;
        int intervalPixelsComplete_;

        public int getPixelsComplete() { return intervalPixelsComplete_; }

        void intervalCallbackCheck()
        {
            updateCallback_();
        }

        // Calculation methods

        void renderSinglePoint()
        {
            double yPosPrevious = -999;
            for(int y = 0; y < cp_.pixelHeight; ++y)
            {
                // Bitmap origin is in top left
                int yBitmap = (cp_.pixelHeight - 1) - y;
                double yPos = cp_.bottom + (System.Convert.ToDouble(y) + 0.5) * cp_.getHeight() / cp_.pixelHeight;
                // Is this the difference between this row and last one below representability?
                if(yPos == yPosPrevious)
                {
                    for(int x = 0; x < cp_.pixelWidth; ++x)
                    {
                        bitmap_.SetPixel(x, yBitmap, System.Drawing.Color.White);
                    }
                }
                else
                {
                    double xPosPrevious = -999;
                    for(int x = 0; x < cp_.pixelWidth; ++x)
                    {
                        double xPos = cp_.left + (System.Convert.ToDouble(x) + 0.5) * cp_.getWidth() / cp_.pixelWidth;
                        // Is this the difference between this column and last one below representability?
                        if(xPos == xPosPrevious)
                        {
                            bitmap_.SetPixel(x, yBitmap, System.Drawing.Color.Red);
                        }
                        else
                        {
                            // Seed interval
                            Math.Complex z = new Complex();
                            Math.Complex c = new Complex();
                            switch(cp_.fractalType)
                            {
                                case CalculationParameters.FractalType.Mandelbrot : 
                                    z = new Math.Complex(0, 0);
                                    c = new Math.Complex(xPos, yPos);
                                    break;
                                case CalculationParameters.FractalType.Julia : 
                                    z = new Math.Complex(xPos, yPos);
                                    c = new Math.Complex(cp_.juliaReal, cp_.juliaImag);
                                    break;
                            }
                            uint i;
                            for(i = 0; i < cp_.iterations; ++i)
                            {
                                z = z * z + c;
                                if(z.magnitude2() > 4.0)
                                {
                                    break;
                                }
                            }                                           
                            if(i < cp_.iterations)
                            {
                                System.Drawing.Color color;
                                if(cp_.simpleGrayscale)
                                {
                                    color = System.Drawing.Color.White;
                                }
                                else
                                {
                                    if(i % 2 == 0) color = System.Drawing.Color.Blue; else color = System.Drawing.Color.LightBlue;
                                }
                                bitmap_.SetPixel(x, yBitmap, color);
                            }
                            else
                            {
                                bitmap_.SetPixel(x, yBitmap, System.Drawing.Color.Black);
                            }
                        }
                        xPosPrevious = xPos;
                    }
                }
                yPosPrevious = yPos;
                updateCallback_();
            }

        }

        // Returns the square of a number
        double square(double n) { return n * n; }

        // Utility class for passing pixel boundaries
        class Boundary
        {
            public Boundary(int leftIn, int bottomIn, int rightIn, int topIn)
            {
                left = leftIn;
                right = rightIn;
                top = topIn;
                bottom = bottomIn;
            }

            public int getWidth() { return right - left; }
            public int getHeight() { return top - bottom; }

            public int left;
            public int right;
            public int top;
            public int bottom;
        }

        enum Membership
        {
            DefinatelyIn, DefinatelyOut, Indeterminate, Unknown
        };

        // Render interval using recursive subdivision (returns max iterations)
        void renderIntervalRecursive(Math.ComplexInterval region, Boundary pixelBounds)
        {
            // Sanity check
            if(pixelBounds.getWidth() <= 0 || pixelBounds.getHeight() <= 0)
            {
                throw new System.Exception("Sanity failure");
            }
            // Is interval invalid? (none of the intervals should have zero width)
            if(region.real().inf() == region.real().sup() ||
                region.imag().inf() == region.imag().sup())
            {
                // Paint the whole region to indicate precision breakdown
                Graphics g = Graphics.FromImage(bitmap_);
                System.Drawing.Rectangle r = new Rectangle(
                    pixelBounds.left,
                    (bitmap_.Height - 1) - pixelBounds.top,
                    pixelBounds.getWidth(),
                    pixelBounds.getHeight());
                g.FillRectangle(System.Drawing.Brushes.Red, r);
            }

            // Test interval for containment

            // Magnitude-two circle
            Math.Interval circleDiameter = new Math.Interval(-2, 2);
            Math.ComplexInterval circleBoundingBox = new Math.ComplexInterval(circleDiameter, circleDiameter);
    
            // Small interval to artificially widen single point seeds
            Math.Interval smallestInterval = new Math.Interval(-System.Double.Epsilon, System.Double.Epsilon);

            // Seed interval
            Math.ComplexInterval z = new ComplexInterval();
            Math.ComplexInterval c = new ComplexInterval();
            switch(cp_.fractalType)
            {
                case CalculationParameters.FractalType.Mandelbrot : 
                    z = new Math.ComplexInterval(new Math.Interval(0), new Math.Interval(0));
                    c = (ComplexInterval)region.Clone();
                    break;
                case CalculationParameters.FractalType.Julia : 
                    z = (ComplexInterval)region.Clone();
                    c = new Math.ComplexInterval(
                        new Math.Interval(cp_.juliaReal) + smallestInterval,
                        new Math.Interval(cp_.juliaImag) + smallestInterval);
                    break;
            }

            ArrayList orbits = new ArrayList();
            Membership m = Membership.Unknown;
            uint iterations;
            for(iterations = 0; 
                m == Membership.Unknown;
                ++iterations)
            {
                // Is the iteration limit active?
                if(cp_.useIterationLimit && iterations > cp_.iterations)
                {
                    m = Membership.DefinatelyIn;
                }
                // Apply equation
                z = Math.ComplexInterval.square(z) + c;
                // Does the interval completely contain the magnitude-2 circle?
                if(Math.ComplexInterval.subset(circleBoundingBox, z))
                {
                    // Set membership for the interval is indeterminate
                    //     as the interval contains points that are both in and out of the set
                    m = Membership.Indeterminate;
                    break;
                }
                // Are all the points outside the magnitude 2 circle?                
                double x0 = square(z.real().inf());
                double x1 = square(z.real().sup());
                double y0 = square(z.imag().inf());
                double y1 = square(z.imag().sup());
                if( x0 + y0 > 4.0 &&
                    x0 + y1 > 4.0 &&
                    x1 + y0 > 4.0 &&
                    x1 + y1 > 4.0)
                {                    
                    // And does the interval not overlap the circle?
                    if(!((Math.Interval.overlap(z.real(), circleDiameter) && z.imag().inf() < 0 && z.imag().sup() > 0) ||
                         (Math.Interval.overlap(z.imag(), circleDiameter) && z.real().inf() < 0 && z.real().sup() > 0)))
                    {
                        // All points in this interval are definately outside the Mandelbrot set
                        m = Membership.DefinatelyOut;
                        break;
                    }
                }
                // Test against previous intervals in the orbit
                foreach(Math.ComplexInterval orbit in orbits)
                {
                    if(Math.ComplexInterval.subset(z, orbit))
                    {
                        // This interval will repeat the contained orbit indefinately
                        m = Membership.DefinatelyIn;
                        break;
                    }
                }
                orbits.Add(z.Clone());
            }
            // Is the set membership known?
            // ....smooth coloring demands full exploration (no recursive shortcuts) of all pixels
            if(!cp_.smoothColoring && m != Membership.Indeterminate)
            {
                System.Drawing.Rectangle r = new Rectangle(
                    pixelBounds.left,
                    bitmap_.Height - pixelBounds.top,
                    pixelBounds.getWidth(),
                    pixelBounds.getHeight());
                System.Drawing.Color color;
                switch(m)
                {
                    case Membership.DefinatelyIn : 
                        color = System.Drawing.Color.Black;
                        break;
                    case Membership.DefinatelyOut :
                        if(cp_.simpleGrayscale)
                        {
                            color = System.Drawing.Color.White;
                        }
                        else
                        {
                            if(iterations % 2 == 0)
                            {
                                color = System.Drawing.Color.Blue; 
                            }
                            else 
                            {
                                color = System.Drawing.Color.LightBlue; 
                            }
                        }
                        break;
                    default : throw new System.Exception("Sanity check: Unexpected Membership state");
                }
                Graphics.FromImage(bitmap_).FillRectangle(new System.Drawing.SolidBrush(color), r);
                intervalPixelsComplete_ += pixelBounds.getWidth() * pixelBounds.getHeight();
                intervalCallbackCheck();
            }
            // Is the region a single pixel?
            else if(pixelBounds.getWidth() == 1 && pixelBounds.getHeight() == 1)
            {
                System.Drawing.Color color;
                switch(m)
                {
                    case Membership.DefinatelyIn :
                        color = System.Drawing.Color.Black;
                        break;
                    case Membership.DefinatelyOut :
                        if(cp_.simpleGrayscale)
                        {
                            color = System.Drawing.Color.White;
                        }
                        else
                        {
                            if(iterations % 2 == 0)
                            {
                                color = System.Drawing.Color.Blue; 
                            }
                            else 
                            {
                                color = System.Drawing.Color.LightBlue; 
                            }
                        }
                        break;
                    case Membership.Indeterminate:
                        if(cp_.simpleGrayscale)
                        {
                            color = System.Drawing.Color.Gray;
                        }
                        else
                        {
                            if(iterations % 2 == 0)
                            {
                                color = System.Drawing.Color.DarkRed; 
                            }
                            else 
                            {
                                color = System.Drawing.Color.Red; 
                            }
                        }
                        break;
                    default : throw new System.Exception("Sanity check: Unexpected Membership state");
                }
                bitmap_.SetPixel(pixelBounds.left, (bitmap_.Height - 1) - pixelBounds.bottom, color);                    
                ++intervalPixelsComplete_;
                intervalCallbackCheck();
            }
            else
            {
                // Sub-divide the remaining space to see if membership can be categorically determined
                int halfWidth = pixelBounds.getWidth() / 2;
                int halfHeight = pixelBounds.getHeight() / 2;
                double midReal = 
                    region.real().inf() + 
                    (region.real().sup() - region.real().inf()) *
                    System.Convert.ToDouble(halfWidth) / System.Convert.ToDouble(pixelBounds.getWidth());
                double midImag =
                    region.imag().inf() + 
                    (region.imag().sup() - region.imag().inf()) *
                    System.Convert.ToDouble(halfHeight) / System.Convert.ToDouble(pixelBounds.getHeight());
                // Top-right
                renderIntervalRecursive(
                    new ComplexInterval(
                        new Interval(midReal, region.real().sup()),
                        new Interval(midImag, region.imag().sup())),
                    new Boundary(
                        pixelBounds.left + halfWidth,
                        pixelBounds.bottom + halfHeight,
                        pixelBounds.right,
                        pixelBounds.top));
                // Bottom-right
                if(halfHeight > 0)
                {
                    renderIntervalRecursive(
                        new ComplexInterval(
                        new Interval(midReal, region.real().sup()),
                        new Interval(region.imag().inf(), midImag)),
                        new Boundary(
                        pixelBounds.left + halfWidth,
                        pixelBounds.bottom,
                        pixelBounds.right,
                        pixelBounds.bottom + halfHeight));
                }
                // Bottom-left
                if(halfWidth > 0 && halfHeight > 0)
                {
                    renderIntervalRecursive(
                        new ComplexInterval(
                            new Interval(region.real().inf(), midReal),
                            new Interval(region.imag().inf(), midImag)),
                        new Boundary(
                            pixelBounds.left,
                            pixelBounds.bottom,
                            pixelBounds.left + halfWidth,
                            pixelBounds.bottom + halfHeight));
                }
                // Top-left
                if(halfWidth > 0)
                {
                    renderIntervalRecursive(
                        new ComplexInterval(
                            new Interval(region.real().inf(), midReal),
                            new Interval(midImag, region.imag().sup())),
                        new Boundary(
                            pixelBounds.left,
                            pixelBounds.bottom + halfHeight,
                            pixelBounds.left + halfWidth,
                            pixelBounds.top));
                }
            }
        }
    }
}
